/**
 * PHOSIDE: PHosphorylation Site IDentification Engine.
 * Copyright 2009 Digital Biology Lab, University of Missouri.
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or (at your option) any later
 * version. <p/> This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more
 * details.
 */

package phoside.data.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import phoside.PhosphoProteins;
import phoside.PhosphoProteins.PhosphoType;
import phoside.Protein;
import phoside.Proteins;
import phoside.ProteinsImpl;

import phoside.util.StaticFinalValues;

/**
 *
 * @author gjj
 */
public class PhosphoDataUtil {

    public static TreeSet<Integer> getSites(final Protein protein) {
        if (protein==null) {
            throw new IllegalArgumentException("protein cannot be null");
        }

        Object obj = protein.getInfo(StaticFinalValues.SITES);
        if (obj==null)
            return new TreeSet();

        try {
            return new TreeSet((Set<Integer>)obj);
        } catch (java.lang.ClassCastException e) {
            return new TreeSet();
        }
    }

    public static void addSite(final Protein protein, final int site,
            List<String> notes) {
        if (protein==null) {
            throw new IllegalStateException("Add protein first.");
        }

        String proteinSeq = protein.getSequence();
        if (site<0 || site>=proteinSeq.length()) {
            throw new IndexOutOfBoundsException("Site out of protein boundary.");
        }

        char c = proteinSeq.charAt(site);
        if (!PhosphoProteins.PhosphoType.phosphorylatableAminoAcids().contains(c)) {
            throw new IllegalStateException("This amino acid cannot be phosphorylated.");
        }

        Set<Integer> sites = getSites(protein);
        if (sites==null || sites.isEmpty()) {
            sites = new TreeSet<Integer>();
            protein.putInfo(StaticFinalValues.SITES, sites);
        }

        sites.add(site);

        if (notes==null||notes.isEmpty())
            return;

        Map<Integer,List<String>> mapSiteNotes;
        Object obj = protein.getInfo(StaticFinalValues.SITENOTES);
        if (obj!=null) {
            mapSiteNotes = (Map<Integer,List<String>>)obj;
        } else {
            mapSiteNotes = new HashMap();
            protein.putInfo(StaticFinalValues.SITENOTES, mapSiteNotes);
        }

        List<String> currNotes = mapSiteNotes.get(site);
        if (currNotes==null) {
            currNotes = new ArrayList();
            mapSiteNotes.put(site, currNotes);
        }
        currNotes.addAll(notes);
    }

    public static void addSites(final Protein protein, Set<Integer> sites) {
        if (protein==null) {
            throw new IllegalStateException("Add protein first.");
        }

        if (sites==null) {
            throw new NullPointerException("Null sites");
        }

        if (protein==null) {
            throw new IllegalStateException("Add protein first.");
        }

        String proteinSeq = protein.getSequence();
        for (int site : sites) {
            if (site<0 || site>=proteinSeq.length()) {
                throw new IndexOutOfBoundsException("Site out of protein boundary.");
            }

            char c = proteinSeq.charAt(site);
            if (!PhosphoProteins.PhosphoType.phosphorylatableAminoAcids().contains(c)) {
                throw new IllegalStateException("the amino acid '"+c+"' cannot be phosphorylated.");
            }
        }

        Set<Integer> curr_sites = getSites(protein);
        if (curr_sites==null || curr_sites.isEmpty()) {
            curr_sites = new TreeSet<Integer>();
            protein.putInfo(StaticFinalValues.SITES, curr_sites);
        }

        curr_sites.addAll(sites);
    }

    public static List<String> getSiteNotes(final Protein protein, int site) {
        if (protein==null) {
            throw new IllegalArgumentException("protein cannot be null");
        }

        Object obj = protein.getInfo(StaticFinalValues.SITENOTES);
        if (obj==null)
            return new ArrayList();

        try {
            return new ArrayList<String>((List<String>)obj);
        } catch (java.lang.ClassCastException e) {
            return new ArrayList();
        }
    }

    public static int countPhosphoProteins(final PhosphoProteins phosphoData,
            final PhosphoType type) {
        if (phosphoData==null) {
            throw new IllegalArgumentException();
        }

        Iterator<Protein> it = phosphoData.proteinIterator();
        int count = 0;
        while (it.hasNext()) {
            Protein protein = it.next();
            String proteinSeq = protein.getSequence();
            Set<Integer> phosphoSites = phosphoData.getSites(protein.getAccession());
            if (phosphoSites==null || phosphoSites.isEmpty()) continue;

            for (int site : phosphoSites) {
                if (type==null || type.matches(proteinSeq.charAt(site))) {
                    count++;
                    break;
                }
            }
        }

        return count;
    }

    public static int countPhosphosites(final PhosphoProteins phosphoData,
            final PhosphoType type) {
        if (phosphoData==null) {
            throw new IllegalArgumentException();
        }

        Iterator<Protein> it = phosphoData.proteinIterator();
        int count = 0;
        while (it.hasNext()) {
            Protein protein = it.next();
            String acc = protein.getAccession();
            String proteinSeq = protein.getSequence();
            Set<Integer> phosphoSites = phosphoData.getSites(acc);
            if (phosphoSites==null) continue;

            for (int site : phosphoSites) {
                if (site>=proteinSeq.length()) {
                    System.err.println("Error: site " + (site+1) + "are outside of protein "+acc);
                    continue;
                }

                if (type==null || type.matches(proteinSeq.charAt(site))) {
                    count++;
                }
            }
        }

        return count;
    }

    public static int countResidue(final PhosphoProteins phosphoData,
            final char ch) {
        if (phosphoData==null) {
            throw new IllegalArgumentException();
        }

        Iterator<Protein> it = phosphoData.proteinIterator();
        int count = 0;
        String key = (""+ch).toUpperCase();
        while (it.hasNext()) {
            Protein protein = it.next();
            String proteinSeq = protein.getSequence().toUpperCase();
            count += org.apache.commons.lang.StringUtils.countMatches(proteinSeq, key);
        }

        return count;
    }

    public static final int UNION = 0;
    public static final int INTERSECTION = 1;
    public static final int DIFFERENCE = 2;

    public static Proteins mergeProteins(List<Proteins> proteinsList) {
        return mergeProteins(proteinsList, UNION);
    }

    public static Proteins mergeProteins(List<Proteins> proteinsList,
            int operation) {
        if (proteinsList==null || proteinsList.size()<2) {
            throw new java.lang.IllegalArgumentException("Number of Proteins should be larger than 2.");
        }

        Proteins res = new ProteinsImpl();
        switch(operation) {
            case UNION: {
                for (Proteins proteins : proteinsList) {
                    Iterator<Protein> it = proteins.proteinIterator();
                    while (it.hasNext()) {
                        Protein protein = it.next();
                        String acc = protein.getAccession();
                        Protein curr = res.getProtein(acc);
                        Protein merged = mergeProteins(curr, protein);
                        res.addProtein(merged);
                    }
                }
                break;
            }
            case INTERSECTION: {
                Proteins first = proteinsList.get(0);
                int n = proteinsList.size();
                Iterator<Protein> it = first.proteinIterator();
                while (it.hasNext()) {
                    Protein protein = it.next();
                    String acc = protein.getAccession();
                    List<Protein> pros = new ArrayList();
                    for (int i=1; i<n; i++) {
                        Protein pro = proteinsList.get(i).getProtein(acc);
                        if (pro==null) break;
                        pros.add(pro);
                    }

                    if (pros.size()==n-1) {
                        Protein merged = protein;
                        for (Protein pro:pros) {
                            merged = mergeProteins(merged, pro);
                        }
                        res.addProtein(merged);
                    }
                }
                break;
            }
            case DIFFERENCE: {
                if (proteinsList.size()>2) {
                    throw new java.lang.IllegalArgumentException("DIFFERENCE operation" +
                            "can only be performed on two proteins.");
                }

                Proteins first = proteinsList.get(0);
                Proteins second = proteinsList.get(1);
                Iterator<Protein> it = first.proteinIterator();
                while (it.hasNext()) {
                    Protein protein = it.next();
                    if (second.getProtein(protein.getAccession())==null) {
                        res.addProtein(protein);
                    }
                }
                break;
            }
            default:
                throw new java.lang.UnsupportedOperationException();
        }

        return res;
    }

    public static Protein mergeProteins(Protein pro1, Protein pro2) {
        if (pro1==null) {
            return pro2;
        } else {
            String seq = pro2.getSequence();
            if (seq!=null) {
                String currSeq = pro1.getSequence();
                if (currSeq==null) {
                    pro1.setSequence(seq);
                } else {
                    if (!currSeq.equalsIgnoreCase(seq)) {
                        return pro2;
                    }
                }
            }

            Set<Integer> sites = getSites(pro2);
            if (sites!=null && !sites.isEmpty()) {
                Set<Integer> newSites = new TreeSet(getSites(pro1));
                newSites.addAll(sites);
                pro1.putInfo(StaticFinalValues.SITES, newSites);

                for (int site : sites) {
                    List<String> notes = getSiteNotes(pro2, site);
                    if (notes==null) continue;

                    List<String> currNotes = getSiteNotes(pro1, site);
                    if (currNotes==null)
                        currNotes = notes;
                    else {
                        // merge notes
                        for (String note : notes) {
                            if (!currNotes.contains(note)) {
                                currNotes.add(note);
                            }
                        }
                    }

                    addSite(pro2, site, notes);
                }
            }

            pro1.copyFrom(pro2, false, null); // copy all fields without replacement
            
            return pro1;
        }
    }

    public static void processProteins(Proteins proteins,
            ProteinProcessor processor) {
        if (proteins==null || processor==null) {
            throw new IllegalArgumentException();
        }

        Iterator<Protein> it = proteins.proteinIterator();
        while (it.hasNext()) {
            Protein protein = it.next();
            processor.process(protein);
        }
    }

}
